/**
 * Mask.java
 */
package org.dicr.util.net;

import java.io.*;

/**
 * Network mask.
 * 
 * @author Igor A Tarasov, &lt;java@dicr.org&gt;
 * @version 060125
 */
public final class Mask implements Comparable<Mask>, Serializable, Cloneable {
	private static final long serialVersionUID = 1L;

	/** Signle-address mask */
	public static Mask SINGLE = new Mask();

	/** Static initializations */
	static {
		SINGLE.value = 0xFFFFFFFF;
	}

	/** Mask value */
	protected int value = 0;

	/**
     * Constructor for internal purpose.
     */
	protected Mask() {
		super();
	}

	/**
     * @param aValue
     * @throws IncorrectAddressException
     */
	public Mask(byte[] aValue) throws IncorrectAddressException {
		if (aValue == null) throw new IllegalArgumentException("null value");
		int intval = IP.parse(aValue);
		Mask.check(intval);
		this.value = intval;
	}

	/**
     * Constructor.
     * 
     * @param aValue
     * @throws IncorrectAddressException
     */
	public Mask(int aValue) throws IncorrectAddressException {
		super();
		Mask.check(aValue);
		this.value = aValue;
	}

	/**
     * @param aValue
     * @throws IncorrectAddressException
     */
	public Mask(String aValue) throws IncorrectAddressException {
		super();
		if (aValue == null) throw new IllegalArgumentException("null value");
		int intval = IP.parse(aValue);
		Mask.check(intval);
		this.value = intval;
	}

	/**
     * Преобразует разрядность в число адресов.
     * 
     * @param bits разрядность (0 &lt;= bits &lt;= 32).
     * @return число адресов.
     * @throws IllegalArgumentException некорректная разрядность.
     */
	public static long bits2count(byte bits) throws IllegalArgumentException {
		if ((bits < 0) || (bits > 32)) throw new IllegalArgumentException("bits: " + bits);
		return 0x100000000L >> bits;
	}

	/**
     * Преобразует разрядность сети в маску.
     * 
     * @param bits разрядность (0 &lt;= bits &lt;= 32).
     * @return маска.
     * @throws IllegalArgumentException некорректная разрядность.
     */
	public static int bits2mask(byte bits) throws IllegalArgumentException {
		if ((bits < 0) || (bits > 32)) throw new IllegalArgumentException("bits: " + bits);
		return (int) (0xFFFFFFFFL << (32 - bits));
	}

	/**
     * Check if value can be netmask.
     * 
     * @param value
     * @throws IncorrectAddressException
     */
	public static void check(int value) throws IncorrectAddressException {
		int mask = value;
		while ((mask & 0x80000000) != 0) {
			mask <<= 1;
		}
		if (mask != 0) throw new IncorrectAddressException(String.format("mask: %08X", Integer.valueOf(value)));
	}

	/**
     * Преобразует число адресов в разрядность сети.
     * 
     * @param value число адресов (0 &lt;= value &lt;= 0x100000000).
     * @return разрядность.
     * @throws IllegalArgumentException некорректное число адресов.
     */
	public static byte count2bits(long value) throws IllegalArgumentException {
		if ((value < 0) || (value > 0x100000000L)) throw new IllegalArgumentException("count: " + value);
		byte bits = 0;
		if (value == 0) bits = 32;
		else {
			int count = (int) ~(value - 1);
			while ((count & 0x80000000) != 0) {
				bits++;
				count <<= 1;
			}
		}
		return bits;
	}

	/**
     * Преобразует число адресов в маску.
     * 
     * @param value число адресов (0 &lt;= value &lt;= 0x100000000).
     * @return маска.
     * @throws IllegalArgumentException некорректное число адресов.
     */
	public static int count2mask(long value) throws IllegalArgumentException {
		if ((value < 0) || (value > 0x100000000L)) throw new IllegalArgumentException("count: " + value);
		int mask = 0;
		if (value == 0) mask = 0xFFFFFFFF;
		else {
			int count = (int) ~(value - 1);
			while ((count & 0x80000000) != 0) {
				mask = (mask >> 1) | 0x80000000;
				count <<= 1;
			}
		}
		return mask;
	}

	/**
     * Caclucate count bits of network.
     * 
     * @param value mask
     * @return number of network bits.
     * @throws IncorrectAddressException
     */
	public static byte mask2bits(int value) throws IncorrectAddressException {
		byte bits = 0;
		int mask = value;
		while ((mask & 0x80000000) != 0) {
			bits++;
			mask <<= 1;
		}
		if (mask != 0) throw new IncorrectAddressException("netmask: " + IP.toString(value));
		return bits;
	}

	/**
     * Преобразует маску в число адресов.
     * 
     * @param value маска.
     * @return число адресов в сети.
     * @throws IncorrectAddressException некорректная маска.
     */
	public static long mask2count(int value) throws IncorrectAddressException {
		long count = 0x100000000L;
		int mask = value;
		while ((mask & 0x80000000) != 0) {
			count >>= 1;
			mask <<= 1;
		}
		if (mask != 0) throw new IncorrectAddressException("netmask: " + IP.toString(value));
		return count;
	}

	/**
     * Convert to bytes.
     * 
     * @return byte[4]
     */
	public byte[] toBytes() {
		return IP.toBytes(this.value);
	}

	/**
     * Convert to interger.
     * 
     * @return int value
     */
	public int toInteger() {
		return this.value;
	}

	/**
     * Convert to string.
     * 
     * @return string value
     */
	@Override
	public String toString() {
		return IP.toString(this.value);
	}

	/**
     * Return address count.
     * 
     * @return count
     */
	public long getAddressCount() {
		long count = 0;
		try {
			count = Mask.mask2count(this.value);
		} catch (IncorrectAddressException ex) {
			throw new Error(ex);
		}

		return count;
	}

	/**
     * Return prefix bits count.
     * 
     * @return count
     */
	public byte getBitsCount() {
		byte bits = 0;
		try {
			bits = Mask.mask2bits(this.value);
		} catch (IncorrectAddressException ex) {
			throw new Error(ex);
		}

		return bits;
	}

	/**
     * Clone Mask.
     * 
     * @return clone
     */
	@Override
	public Mask clone() {
		Mask mask = new Mask();
		mask.value = this.value;
		return mask;
	}

	/**
     * @param m
     * @return -1, 0, 1
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
	@Override
    public int compareTo(Mask m) {
		try {
			byte bits1 = Mask.mask2bits(this.value);
			byte bits2 = Mask.mask2bits(m.value);
			if (bits1 < bits2) return 1;
			else if (bits1 > bits2) return -1;
			return 0;
		} catch (IncorrectAddressException ex) {
			throw new Error(ex);
		}
	}

	/**
     * @see java.lang.Object#hashCode()
     */
	@Override
	public int hashCode() {
		final int PRIME = 31;
		int result = 1;
		result = PRIME * result + this.value;
		return result;
	}

	/**
     * @see java.lang.Object#equals(java.lang.Object)
     */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		if (getClass() != obj.getClass()) return false;
		final Mask other = (Mask) obj;
		if (this.value != other.value) return false;
		return true;
	}
}
